using Velocity4Net.Ctx;
using Velocity4Net.Runtime.Parse.AST;
using System.IO;
using System;
using Velocity4Net.Runtime.Parse;
using Velocity4Net.Errors;
using Velocity4Net.Util.Introspection;
namespace Velocity4Net.Runtime.Directives
{
    public class Evaluate : Directive
    {

        /**
         * Return name of this directive.
         * @return The name of this directive.
         */
        public override String Name
        {
            get
            {
                return "evaluate";
            }
            set { }
        }

        /**
         * Return type of this directive.
         * @return The type of this directive.
         */
        public override int Type
        {
            get
            {
                return 2;
            }
        }

        /**
         * Initialize and check arguments.
         * @param rs
         * @param context
         * @param node
         * @throws TemplateInitException
         */
        public override void Init(RuntimeServices rs, InternalContextAdapter context,
                         INode node)
        {
            base.Init(rs, context, node);

            /**
             * Check that there is exactly one argument and it is a string or reference.
             */

            int argCount = node.jjtGetNumChildren();
            if (argCount == 0)
            {
                throw new TemplateInitException(
                        "#" + Name + "() requires exactly one argument",
                        context.CurrentTemplateName,
                        node.Column,
                        node.Line);
            }
            if (argCount > 1)
            {
                /* 
                 * use line/col of second argument
                 */

                throw new TemplateInitException(
                        "#" + Name + "() requires exactly one argument",
                        context.CurrentTemplateName,
                        node.jjtGetChild(1).Column,
                        node.jjtGetChild(1).Line);
            }

            INode childNode = node.jjtGetChild(0);
            if (childNode.NodeType != ParserTreeConstants.JJTSTRINGLITERAL &&
                 childNode.NodeType != ParserTreeConstants.JJTREFERENCE)
            {
                throw new TemplateInitException(
                        "#" + Name + "()  argument must be a string literal or reference",
                        context.CurrentTemplateName,
                        childNode.Column,
                        childNode.Line);
            }
        }

        /**
         * Evaluate the argument, convert to a String, and evaluate again 
         * (with the same context).
         * @param context
         * @param writer
         * @param node
         * @return True if the directive rendered successfully.
         * @throws IOException
         * @throws ResourceNotFoundException
         * @throws ParseErrorException 
         * @throws MethodInvocationException
         */
        public override bool Render(InternalContextAdapter context, TextWriter writer,
                INode node)
        {

            /*
             * Evaluate the string with the current context.  We know there is
             * exactly one argument and it is a string or reference.
             */

            Object value = node.jjtGetChild(0).GetValue(context);
            String sourceText;
            if (value != null)
            {
                sourceText = value.ToString();
            }
            else
            {
                sourceText = "";
            }

            /*
             * The new string needs to be parsed since the text has been dynamically generated.
             */
            String templateName = context.CurrentTemplateName;
            SimpleNode nodeTree = null;

            try
            {
                nodeTree = rsvc.Parse(new StringReader(sourceText), templateName, false);
            }
            catch (ParseException pex)
            {
                // use the line/column from the template
                Info info = new Info(templateName, node.Line, node.Column);
                throw new ParseErrorException(pex.Message, info);
            }
            catch (TemplateInitException pex)
            {
                Info info = new Info(templateName, node.Line, node.Column);
                throw new ParseErrorException(pex.Message, info);
            }

            /*
             * now we want to init and render.  Chain the context
             * to prevent any changes to the current context.
             */

            if (nodeTree != null)
            {
                InternalContextAdapter ica = new EvaluateContext(context, rsvc);

                ica.PushCurrentTemplateName(templateName);

                try
                {
                    try
                    {
                        nodeTree.Init(ica, rsvc);
                    }
                    catch (TemplateInitException pex)
                    {
                        Info info = new Info(templateName, node.Line, node.Column);
                        throw new ParseErrorException(pex.Message, info);
                    }

                    try
                    {
                        preRender(ica);

                        /*
                         *  now render, and let any exceptions fly
                         */
                        nodeTree.Render(ica, writer);
                    }
                    catch (StopCommand stop)
                    {
                        if (!stop.isFor(this))
                        {
                            throw stop;
                        }
                        else if (rsvc.getLog().isDebugEnabled())
                        {
                            rsvc.getLog().debug(stop.Message);
                        }
                    }
                    catch (ParseErrorException pex)
                    {
                        // convert any parsing errors to the correct line/col
                        Info info = new Info(templateName, node.Line, node.Column);
                        throw new ParseErrorException(pex.Message, info);
                    }
                }
                finally
                {
                    ica.PopCurrentTemplateName();
                    postRender(ica);
                }
                return true;
            }

            return false;
        }

    }
}